/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package Household;

import MortgageCore.*;
import java.util.*;
import Bank.Bank;
import simframe.simulation.*;
import simframe.util.*;
import generators.HouseholdGenerator;
import generators.HouseholdGenerator.HouseholdInfo;
/**
 *
 * @author karakots
 */
 public class Household extends SimObject implements iBuyerSeller, iDepositor, iGoverned {

    // swarm actions
    // Actions that households perform
    public static final Swarm.SwarmAction<Household> newDay = new Swarm.SwarmAction<Household>() {
        public synchronized void exec(Household hh) {
            hh.newDay();
        }
    };
    public static final Swarm.SwarmAction<Household> work = new Swarm.SwarmAction<Household>() {
        public  synchronized void exec(Household hh) {
            hh.work();
        }
    };
    public static final Swarm.SwarmAction<Household> payExpenses = new Swarm.SwarmAction<Household>() {
        public  synchronized void exec(Household hh) {
            hh.payExpenses();
        }
    };
    public static final Swarm.SwarmAction<Household> manageCash = new Swarm.SwarmAction<Household>() {
        public  synchronized void exec(Household hh) {
            hh.manageCash();
        }
    };

     private HomeBehavior myHomeBehavior;
     private HouseholdInfo my_info; //Household info
     private double localCashAssets;
     private String residenceState;
     private double incomePctMedian;
     private double incomePctAverage;
     private int myDayOfMonth;           // day of month this household will be evaluated
     private int dayOfMonth;
     private int daysSinceLastEval;
     private final Location loc;
     private static HouseholdGenerator Generator;
     private JobMarket jobMarket;
     private double employedFactor;
     private double lastDurablePurchaseAmount;
     private double lastSavingsDeposit;
     private double interestIncome;

     private boolean isSelected;
     public boolean logViewOpen;

     
     public static final int kNoSavingsAccount = -1;
     public static final int kDaysPerMonth = 30;
     private static int numJobsNeedToAdd = 0;


     //
     // accessors
     //
     public geography.Geography.City getCity()          {return my_info.city; }
     public boolean getSelected()                       {return isSelected;}
     public void setSelected(boolean selected)          {isSelected = selected;}
     public Location getLoc()                           {return loc;}
     public double getcashAssets()                      {return localCashAssets;}
     public void addCash(double amount)                 {localCashAssets += amount;}
     public void payCash(double amount)                 {localCashAssets -= amount;}
     public Bank getSavingsBank()                       {return savingsBank;}
     public int getDaysSinceLastEval()                  {return daysSinceLastEval;}
     public double getMonthlySalary()                   {return my_info.salary * employedFactor;}
     public double getExpenses()                        {return my_info.expenses * my_info.cost_of_living;}
     public void setJobMarket(JobMarket j)              {jobMarket = j;}
     public double getLastDurablePurchaseAmount()       {return lastDurablePurchaseAmount;}
     public int getMyDayOfMonth()                       {return myDayOfMonth;}
     public int getDayOfMonth()                         {return dayOfMonth;}
     public boolean isEvaluatingToday()                 {return dayOfMonth == myDayOfMonth;}
     public double getTotalCashAssets()                 {return getSavings() + localCashAssets + myHomeBehavior.getInitialDownPaymentBuffer();}
     private double getExpectedLocalCash()              {return localCashAssets;}
     public String getCityName()                        {return my_info.city.GetName();}
     public String getStateName()                       {return my_info.city.GetState().getStateCode();}
     public int getNumPeople()                          {return my_info.people.size();}
     public String getFamilyStatus()                    {return my_info.type;}
     public double getIncomePctMedian()                 {return incomePctMedian;}
     public double getIncomePctAverage()                {return incomePctAverage;}
     public String getResidenceState()                  {return residenceState;}
     public HouseholdInfo getMyInfo()                   {return my_info;}
     public double getLastSavingsDeposit()              {return lastSavingsDeposit;}

     //
     // initiailization
     //
     public static void InitializeGenerator(int num_households)
     {
         if (Generator == null) {
             try {
                 Generator = new HouseholdGenerator(num_households);
             } catch (java.io.IOException e) {
             }
         }
     }

     public void initializeHomeStatus() {
         double medianIncome = Generator.getMedianIncome(residenceState);
         incomePctMedian = my_info.salary / medianIncome;
         double averageIncome = Generator.getAverageIncome(residenceState);
         incomePctAverage = my_info.salary /averageIncome;
     }

     public Household() {
         InitializeGenerator(1000);
         my_info = Generator.GetNext();

         // fuzz it up a little
         double x = my_info.city.GetLongitude() + 2* geography.Geography.LongitudeNoise * (Math.random() - 1);
         double y = my_info.city.GetLatitude()  + 2*geography.Geography.LatitudeNoise * (Math.random() - 1);

         // maybe get assigned a house
         residenceState = my_info.city.GetState().getStateCode();

         loc = new Location(x, y);
         savingsBank = null;
         localCashAssets = 0.0;
         savingsRateScale = 1.0;
         savingsAccountNumber = kNoSavingsAccount;
         myDayOfMonth = 1 + (int) (Math.random() * kDaysPerMonth);
         dayOfMonth = 0;
         isSelected = false;
         logViewOpen = false;
         daysSinceLastEval = 0;
         
         myHomeBehavior = new HomeBehavior(this);
     }

     //
     // basic simulation of household
     //

     private void newDay() {
         if (dayOfMonth++ >= kDaysPerMonth) {
             dayOfMonth = 1;
             myHomeBehavior.rentTimer();
         }
         daysSinceLastEval += 1;
         if (dayOfMonth == myDayOfMonth)
             daysSinceLastEval = 0;

         if (intializeSavings)
             initialDeposit();

         overdraftCheck();
     }


     private void checkEmployment() {
         // make up for any other HH for which we couldn;t ad or subtract jobs
         double emplDelta = 0.5;
        
         if ((numJobsNeedToAdd > 0) && (employedFactor < 1)) {
             employedFactor += emplDelta;
             myHomeBehavior.jobGain();
             numJobsNeedToAdd -= 1;
             return;
         } else if ((numJobsNeedToAdd < 0) && (employedFactor > 1)) {
             employedFactor -= emplDelta;
             myHomeBehavior.jobLoss();
             numJobsNeedToAdd += 1;
             return;
         }

         // if we are still here then try to really add or remove jobs
         double emplChange = jobMarket.getEmploymentChange(getResidenceState());
         // we wil use double the change value, because it is so small, and increment the empl numer by 0.5
         emplChange *= 2;
         double limit = Math.abs(emplChange);
         if (Math.random() > limit)
             return;

         // check for job gains
         if (emplChange > 0) {
                 if (employedFactor < my_info.num_members) {
                    employedFactor += emplDelta;   // add a job
                    myHomeBehavior.jobGain();
                 } else
                     numJobsNeedToAdd++;
         // or job losses
         } else if (emplChange < 0) {
             if (employedFactor >= emplDelta) {
                 employedFactor -= emplDelta;   // remove a job
                 myHomeBehavior.jobLoss();
             } else
               numJobsNeedToAdd--;
         }
     }

     private void overdraftCheck() {
         if (getExpectedLocalCash() < 0) {
             boolean overDraft = true;
         }
     }

     private void work() {
        if (dayOfMonth != myDayOfMonth) return;
        // receive monthlySalary
        addCash(my_info.salary * employedFactor);
        // possible monthlySalary change
        // posible job loss or gain
        checkEmployment();
    }

    public double getMonthlyNonhousingExpenses() {
        return my_info.expenses * my_info.cost_of_living;
    }

    private void payExpenses() {
        if (dayOfMonth != myDayOfMonth)
            return;

        // basic expenses
        double cashNeeded = getMonthlyNonhousingExpenses();
        cashNeeded += myHomeBehavior.payHomeExpenses();

        // a check to make sure we don't exceed salary
        //if (cashNeeded > my_info.salary) {
       //     cashNeeded = my_info.salary;
       // }

        // get the money to pay the bills
        raiseCash(cashNeeded);
        payCash(cashNeeded);

        // occasionally buy durable goods
        lastDurablePurchaseAmount = 0;
        if ((localCashAssets > 0) && (Math.random() < 0.5)) {
            double cashInBank = savingsBank.getBalance(savingsAccountNumber);
            //double locaCashToSpend = 0;
            if (cashInBank > 0) {
                double amountToWithdraw;
                if (Math.random() < 0.5) {
                    amountToWithdraw =  cashInBank * Math.random() * 0.6;
                    //locaCashToSpend = localCashAssets * 0.6;
                } else {
                    amountToWithdraw =  cashInBank * Math.random() * 0.2;
                    //locaCashToSpend = localCashAssets * 0.2;
                }
                // spend the money
                savingsBank.withdraw(savingsAccountNumber, amountToWithdraw);
                //payCash(locaCashToSpend);
                lastDurablePurchaseAmount = amountToWithdraw;// + locaCashToSpend;

            }
        }

        // perhaps save money
        /*
        if ((localCashAssets > 0) && (lastDurablePurchaseAmount == 0)) {
            double savingsPercent = my_info.savings_rate * savingsRateScale;
            if (savingsPercent > 1.0)
                savingsPercent = 1.0;
            double amountToSave = (my_info.salary -  cashNeeded) * savingsPercent;
            if ((amountToSave > 0) && (savingsBank != null)){
                savingsBank.deposit(savingsAccountNumber, amountToSave);
                payCash(amountToSave);
                lastSavingsDeposit = amountToSave;
            }
        }
        */
    }

    private void manageCash() {
        if (dayOfMonth != myDayOfMonth) return;
        lastSavingsDeposit = 0;
        if (localCashAssets <= 0)
            return;
        double savingsPercent = my_info.savings_rate * savingsRateScale;
        if (savingsPercent > 1.0)
            savingsPercent = 1.0;
        double amountToSave = localCashAssets * savingsPercent;
        if (savingsBank != null) {
            savingsBank.deposit(savingsAccountNumber, amountToSave);
            payCash(amountToSave);
            lastSavingsDeposit = amountToSave;
        }
        
        // deposit to bank
        // withdraw from bank
    }


     //
     // Financial Utilities
     //
     public boolean raiseCash(double amount) {
        boolean gotCash = false;
        if (getExpectedLocalCash() >= amount) {
            gotCash = true;
        } else {
            double savings = savingsBank.getBalance(savingsAccountNumber);
            double amountToWithDraw = amount - getExpectedLocalCash();
            if (savings >= amountToWithDraw) {
                if (savingsBank.withdraw(savingsAccountNumber, amountToWithDraw) == amountToWithDraw) {
                    gotCash = true;
                    addCash(amountToWithDraw);
                }
            }
        }
        return gotCash;
    }

    // generates cash needed by goign negative locally
    public boolean raiseCashAnyway(double amount) {
        if (getExpectedLocalCash() >= amount) {
            ;
        } else {
            double savings = savingsBank.getBalance(savingsAccountNumber);
            double amountToWithDraw = amount - getExpectedLocalCash();
            if (savings >= amountToWithDraw) {
                if (savingsBank.withdraw(savingsAccountNumber, amountToWithDraw) == amountToWithDraw) {
                    addCash(amountToWithDraw);
                }
            } else {
               savingsBank.withdraw(savingsAccountNumber, savings);
               addCash(savings);
            }
        }
        return true;
    }


     //
     // Savings Account
     //
     private boolean intializeSavings;
     private Bank savingsBank;
     private double savingsRateScale;
     private int savingsAccountNumber;
     public int getAccountNumber()                      {return savingsAccountNumber;}
     public double getSavings()                         {return savingsBank.getBalance(savingsAccountNumber);}
     public double getSavingsRate() {
         double rVal = my_info.savings_rate * savingsRateScale;
         if (rVal > 1.0) rVal = 1.0;
         return rVal;
     }

     public synchronized void interestPaidThisPeriod(double amount)
     {
         interestIncome = amount;
     }

     private void initialDeposit() {
         double deposit;
         intializeSavings = false;
         deposit = (my_info.salary - my_info.expenses) * 12 * Math.random();
         if (savingsBank != null)
             savingsBank.deposit(savingsAccountNumber, deposit);
     }

    public void openAccount(Bank bank)
    {
        savingsBank = bank;
        //double deposit = (my_info.salary - my_info.expenses) * 120 * Math.random();
        savingsAccountNumber = savingsBank.openAccount(this, 0);

        if ( savingsAccountNumber == kNoSavingsAccount) {
            savingsBank = null;
        }
    }

    public void updateSavingsRateScale(double rate) {
        this.savingsRateScale = rate;
    }

    public void savingsAccountMoved(int oldAccountNum, Bank oldBank, int newAccountNum, Bank newBank) {
        this.savingsAccountNumber = newAccountNum;
        this.savingsBank = newBank;
    }


    //
    // renting, buying and selling homes
    //
    public HomeBehavior hb()                                        {return myHomeBehavior;};

    public static final Swarm.SwarmAction<Household> considerHousingAction = new Swarm.SwarmAction<Household>() {
        public  synchronized void exec(Household hh) {
            hh.myHomeBehavior.considerHousingAction();
        }
    };
    public Action mortgageFunded(final Mortgage mortgage) {
            return new Action() { public synchronized void exec() {
                 myHomeBehavior.mortgageFundedMessage(mortgage);
             }
         };
    }
    public synchronized void offerAccepted(MortgageOffer offer)     {myHomeBehavior.offerAccepted(offer);}
    public synchronized void offerExpired(MortgageOffer offer)      {myHomeBehavior.offerExpired(offer);}
    public synchronized void mortgagePreapproval(Mortgage m)        {myHomeBehavior.mortgagePreapproval(m);}
    public synchronized void preAppMortgageExpired(Mortgage m)      {myHomeBehavior.preAppMortgageExpired(m);}
    public synchronized void mortgagePaidInFull(Mortgage m)         {myHomeBehavior.mortgagePaidInFull(m);}
    // I think we will end up not needing thiese....
    synchronized public void houseFound(House house)                {}
    public void buyerFound(iBuyerSeller buyer)                      {}
    public void payCashForHouse(iBuyerSeller buyer, House house, double payment) {}
    public void hereIsTitle(House house, Title title)               {}

    //
    // iGoverned Interface
    //
    public void updateRegsAndExternals(Regulations regs, Externals ext) {}


    //
    // SimObject Inerface
    //
    public boolean dead() {
        return false;
    }

    public void reset() {

        this.localCashAssets = 0.0;
        this.dayOfMonth = 0;
        intializeSavings = true;
        myHomeBehavior.reset();
        employedFactor = 1;
        numJobsNeedToAdd = 0;
        lastDurablePurchaseAmount = 0;
        lastSavingsDeposit = 0;
        interestIncome = 0;
    }
    
    //
    //Metrics
    //
     public static List<SwarmMetric<Household>> getMetrics() {

         List<SwarmMetric<Household>> rval = new ArrayList<SwarmMetric<Household>>();

         SwarmMetric<Household> metric;

         // savings: average savings per HH
         metric = new SwarmMetric<Household>("Avg. HH Savings", Metric.Units.Dollars,
                 new Compute<Household>() {
                     public double exec(Household obj) {
                         return obj.getSavings();
                     }
                 });
         metric.setNumericalRange(new double[]{0, 10000, 50000, 100000, 200000, 500000, 800000, 1000000});
         rval.add(metric);

         /*
         metric = new SwarmMetric<Household>("Household Cash Assets", Metric.Units.Dollars,
                 new Compute<Household>() {

                     public double exec(Household obj) {
                         return obj.getcashAssets();
                     }
                 });

         metric.setNumericalRange(new double[]{0, 10000, 50000, 100000, 200000, 500000, 800000, 1000000});
         rval.add(metric);
         */

         // percent of households with funded mortgages that  missed a payment in the previous 30 days
         metric = new SwarmMetric<Household>("% Missed payment last 30 days", Metric.Units.Percent,
                 new Compute<Household>() {
                     public double exec(Household obj) {
                         return obj.hb().getMissedPaymentLastMonth() ? 1 : 0;
                     }
                 });
         metric.setNumericalRange(new double[]{0, 0.01, 0.02, 0.03, 0.04, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 1.0});
         metric.setConstraint(new Metric.Constrain<Household>() {
             public boolean allowed(Household obj) {
                 return obj.myHomeBehavior.getMortgage() != null;
             }
         });
         rval.add(metric);

         // Preapproved is not currently an interestingmetric, because any HH that is preapproved will get a mortgage
         /*
         metric = new SwarmMetric<Household>("PreApproved", Metric.Units.Percent,
                 new Compute<Household>() {
                     public double exec(Household obj) {
                         return obj.hb().getHasPreApprovedLoan() ? 1.0 : 0.0;
                     }
                 });
         metric.setNumericalRange(new double[]{0, 0.01, 0.05, 0.1, 0.2, 0.5, 1.0});
         rval.add(metric);
         */

         // Missed payments is the same as in default, so we'll skip it
         /*
         metric = new SwarmMetric<Household>("Missed mortgage payments", Metric.Units.Percent,
                 new Compute<Household>() {
                     public double exec(Household obj) {
                         return obj.hb().getNumMissedMortgagePayments();
                     }
                 });
         metric.setNumericalRange(new double[]{0, 0.01, 0.02, 0.03, 0.04, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 1.0});
         metric.setConstraint(new Metric.Constrain<Household>() {
             public boolean allowed(Household obj) {
                 return obj.myHomeBehavior.getMortgage() != null;
             }
         });
         rval.add(metric);
         */

         // Percent of HH's that own homes that moved in the previous month
         metric = new SwarmMetric<Household>("% Moved last 30 days", Metric.Units.Percent,
                 new Compute<Household>() {
                     public double exec(Household obj) {
                         return obj.myHomeBehavior.getMovedInLastMonth() ? 1 : 0;
                     }
                 });
         metric.setNumericalRange(new double[]{0, 0.01, 0.1, 0.2, 0.3, 0.4, 0.5, 1.0});
         metric.setConstraint(new Metric.Constrain<Household>() {
             public boolean allowed(Household obj) {
                 return obj.myHomeBehavior.getIsHomeowner();
             }
         });
         rval.add(metric);

         // percent of HH's that refinanced in the previous month
         metric = new SwarmMetric<Household>("% Refinanced last 30 days", Metric.Units.Percent,
                 new Compute<Household>() {
                     public double exec(Household obj) {
                         return obj.myHomeBehavior.getRefinancedInLastMonth() ? 1 : 0;
                     }
                 });
         metric.setNumericalRange(new double[]{0, 0.01, 0.1, 0.2, 0.3, 0.4, 0.5, 1.0});
         metric.setConstraint(new Metric.Constrain<Household>() {
             public boolean allowed(Household obj) {
                 return obj.myHomeBehavior.getIsHomeowner();
             }
         });
         rval.add(metric);

         // 
         metric = new SwarmMetric<Household>("Home Quality", Metric.Units.Percent,
                 new Compute<Household>() {
                     public double exec(Household obj) {
                        return obj.hb().getHomeQ();
                     }
                 });
         metric.setNumericalRange(new double[]{0, 0.01, 0.1, 0.2, 0.3, 0.4, 0.5, 1.0});
         metric.setConstraint(new Metric.Constrain<Household>() {
             public boolean allowed(Household obj) {
                 return obj.myHomeBehavior.hasHouse();
             }
         });
         rval.add(metric);

         // Will we see lower end income households increaing home ownership with loose lending?
         metric = new SwarmMetric<Household>("Income %Average Homeowners", Metric.Units.Percent,
                 new Compute<Household>() {
                     public double exec(Household obj) {
                        return obj.getIncomePctAverage();
                     }
                 });
         metric.setNumericalRange(new double[]{0, 0.01, 0.1, 0.2, 0.3, 0.4, 0.5, 1.0});
         metric.setConstraint(new Metric.Constrain<Household>() {
             public boolean allowed(Household obj) {
                 return obj.myHomeBehavior.hasHouse();
             }
         });
         rval.add(metric);

         //
         metric = new SwarmMetric<Household>("Average Homeowner Equity", Metric.Units.Percent,
                 new Compute<Household>() {
                     public double exec(Household obj) {
                        return obj.hb().getHomeEquity();
                     }
                 });
         metric.setNumericalRange(new double[]{0, 0.01, 0.1, 0.2, 0.3, 0.4, 0.5, 1.0});
         metric.setConstraint(new Metric.Constrain<Household>() {
             public boolean allowed(Household obj) {
                 return obj.myHomeBehavior.hasHouse();
             }
         });
         rval.add(metric);
         
         // average household cash asets
         metric = new SwarmMetric<Household>("HH Cash on Hand", Metric.Units.Dollars,
                 new Compute<Household>() {
                     public double exec(Household obj) {
                        return obj.localCashAssets;
                     }
                 });
         metric.setNumericalRange(new double[]{0, 0.01, 0.1, 0.2, 0.3, 0.4, 0.5, 1.0});
         rval.add(metric);

         // income
         metric = new SwarmMetric<Household>("Monthly salary", Metric.Units.Percent,
                 new Compute<Household>() {
                     public double exec(Household obj) {
                        return obj.getMonthlySalary();
                     }
                 });
         metric.setNumericalRange(new double[]{0, 0.01, 0.1, 0.2, 0.3, 0.4, 0.5, 1.0});
         rval.add(metric);

         // interest income
         metric = new SwarmMetric<Household>("Monthly interest income", Metric.Units.Percent,
                 new Compute<Household>() {
                     public double exec(Household obj) {
                        return obj.interestIncome;
                     }
                 });
         metric.setNumericalRange(new double[]{0, 0.01, 0.1, 0.2, 0.3, 0.4, 0.5, 1.0});
         rval.add(metric);

         // housing expenses (% of income)
         metric = new SwarmMetric<Household>("%Income: Mortgage or Rent", Metric.Units.Percent,
                 new Compute<Household>() {
                     public double exec(Household obj) {
                         double income = obj.interestIncome + obj.getMonthlySalary();
                         if (income == 0)
                             return 0;
                         double housingExpense;
                         if (obj.hb().hasHouse()) {
                             if (obj.hb().getMortgage() != null)
                                 housingExpense =  obj.hb().getMortgage().getMonthlyPayment();
                             else
                                 housingExpense = 0;
                         }
                         else
                            housingExpense = obj.hb().getRent();
                         return (housingExpense/income);
                     }
                 });
         metric.setNumericalRange(new double[]{0, 0.01, 0.1, 0.2, 0.3, 0.4, 0.5, 1.0});
         metric.setConstraint(new Metric.Constrain<Household>() {
             public boolean allowed(Household obj) {
                 return (obj.getMonthlySalary() > 0);
             }
         });
         rval.add(metric);

         // Home maintenence % of income
         metric = new SwarmMetric<Household>("%Income: Home Maint. & Taxes", Metric.Units.Percent,
                 new Compute<Household>() {
                     public double exec(Household obj) {
                         double income = obj.interestIncome + obj.getMonthlySalary();
                         if (income == 0)
                             return 0;
                         double expenses = obj.hb().getMonthlyHomeExpenses();
                         return expenses/income;
                     }
                 });
         metric.setNumericalRange(new double[]{0, 0.01, 0.1, 0.2, 0.3, 0.4, 0.5, 1.0});
         metric.setConstraint(new Metric.Constrain<Household>() {
             public boolean allowed(Household obj) {
                 return (obj.myHomeBehavior.hasHouse() && (obj.getMonthlySalary() > 0));
             }
         });
         rval.add(metric);

         // living expenses (% of income)
         metric = new SwarmMetric<Household>("%Income: Living Expenses", Metric.Units.Percent,
                 new Compute<Household>() {
                     public double exec(Household obj) {
                         double income = obj.interestIncome + obj.getMonthlySalary();
                         if (income == 0)
                             return 0;
                         double expenses = obj.getMonthlyNonhousingExpenses();
                         if (expenses > income) {
                             boolean wawaoo = true;
                        }
                         return expenses/income;
                     }
                 });
         metric.setNumericalRange(new double[]{0, 0.01, 0.1, 0.2, 0.3, 0.4, 0.5, 1.0});
         metric.setConstraint(new Metric.Constrain<Household>() {
             public boolean allowed(Household obj) {
                 return (obj.getMonthlySalary() > 0);
             }
         });
         rval.add(metric);

         // durable goods purchasing (% of income)
         metric = new SwarmMetric<Household>("%Income: Durable Goods", Metric.Units.Percent,
                 new Compute<Household>() {
                     public double exec(Household obj) {
                         double income = obj.interestIncome + obj.getMonthlySalary();
                         if (income == 0)
                             return 0;
                         double expenses = obj.getLastDurablePurchaseAmount();
                         return expenses/income;
                     }
                 });
         metric.setNumericalRange(new double[]{0, 0.01, 0.1, 0.2, 0.3, 0.4, 0.5, 1.0});
         metric.setConstraint(new Metric.Constrain<Household>() {
             public boolean allowed(Household obj) {
                 return (obj.getMonthlySalary() > 0);
             }
         });
         rval.add(metric);

         // savings (% of income)
         metric = new SwarmMetric<Household>("%Income: To Savings", Metric.Units.Percent,
                 new Compute<Household>() {
                     public double exec(Household obj) {
                         double income = obj.interestIncome + obj.getMonthlySalary();
                         if (income == 0)
                             return 0;
                         return obj.getLastSavingsDeposit()/income;
                     }
                 });
         metric.setNumericalRange(new double[]{0, 0.01, 0.1, 0.2, 0.3, 0.4, 0.5, 1.0});
         metric.setConstraint(new Metric.Constrain<Household>() {
             public boolean allowed(Household obj) {
                 return (obj.getMonthlySalary() > 0);
             }
         });
         rval.add(metric);

         return rval;
     }
}
